﻿#region Copyright 
/*-------------------------------------------------------------------------
* 命名空间名称/文件名:    SSPivot.AspNetCore.Repository/AppDbContext 
* CLR版本:     4.0.30319.42000
* 机器名称:    DESKTOP-NHMLO8A
* 功 能：       N/A
* 类 名：       AppDbContext
* 创建时间：  2025/5/22 15:30:06
* 版本号：      v1.0
* 创建人:        xulong
*-------------------------------------------------------------------------*/
#endregion
using ICSharpCode.SharpZipLib.Core;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.ChangeTracking;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using NPOI.SS.Formula.Functions;
using SSPivot.AspNetCore.Abstractions.Data;
using SSPivot.AspNetCore.Abstractions.User;
using SSPivot.AspNetCore.Config;
using SSPivot.Common;
using SSPivot.Common.Helpers;
using SSPivot.Common.Interface;
using SSPivot.Data;
using SSPivot.Data.Entities;
using SSPivot.Data.MultiTenancy;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;

namespace SSPivot.AspNetCore.Repository
{
    public class AppDbContext<TDbContext> : DbContext where TDbContext : DbContext
    {
        #region 注入   
        private readonly bool _isReadOnly;
        protected virtual bool IsSoftDeleteFilterEnabled => DataFilter?.IsEnabled<IAuditedDeletedEntity>() ?? false;
        protected virtual long? CurrentTenantId => CurrentUser?.TenantId;
        public ILazyServiceProvider LazyServiceProvider { get; set; }
        protected virtual bool IsMultiTenantFilterEnabled => DataFilter?.IsEnabled<ITenantEntity>() ?? false;
        public IDataFilter DataFilter => LazyServiceProvider.LazyGetRequiredService<IDataFilter>(); 
        public ICurrentUser CurrentUser => LazyServiceProvider.LazyGetRequiredService<ICurrentUser>();
        #endregion

        public AppDbContext(DbContextOptions<TDbContext> options )
       : base(options)
        { 
        }
        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            base.OnModelCreating(modelBuilder);
            //modelBuilder.Filter<ISoftDelete>(e => !e.IsDelete);
            foreach (var entityType in modelBuilder.Model.GetEntityTypes())
            {
                ConfigureBasePropertiesMethodInfo
                .MakeGenericMethod(entityType.ClrType)
                .Invoke(this, new object[] { modelBuilder, entityType });

                
            }

        }
        #region 数据过滤
        protected virtual void ConfigureBaseProperties<TEntity>(ModelBuilder modelBuilder, IMutableEntityType mutableEntityType)
        where TEntity : class
        {
            if (mutableEntityType.IsOwned())
            {
                return;
            }
            if (!typeof(IEntity).IsAssignableFrom(typeof(TEntity)))
            {
                return;
            }
            if (modelBuilder.Entity<TEntity>().Metadata.ClrType.IsAssignableTo<ITenantEntity>())
            {
                modelBuilder.Entity<TEntity>().Property(nameof(ITenantEntity.TenantId))
                    .IsRequired(false)
                    .HasColumnName(nameof(ITenantEntity.TenantId));
            }
            ConfigureGlobalFilters<TEntity>(modelBuilder, mutableEntityType);
        }

        protected virtual void ConfigureGlobalFilters<TEntity>(ModelBuilder modelBuilder, IMutableEntityType mutableEntityType)
        where TEntity : class
        {
            if (mutableEntityType.BaseType == null && ShouldFilterEntity<TEntity>(mutableEntityType))
            {
                var filterExpression = CreateFilterExpression<TEntity>();
                if (filterExpression != null)
                {
                    modelBuilder.Entity<TEntity>().HasQueryFilter(filterExpression);
                }
            }
        }

        protected virtual bool ShouldFilterEntity<TEntity>(IMutableEntityType entityType) where TEntity : class
        {
            if (typeof(ITenantEntity).IsAssignableFrom(typeof(TEntity)))
            {
                return true;
            }
            

            if (typeof(IAuditedDeletedEntity).IsAssignableFrom(typeof(TEntity)))
            {
                return true;
            }
            return false;
        }

        protected virtual Expression<Func<TEntity, bool>> CreateFilterExpression<TEntity>()
        where TEntity : class
        {
            Expression<Func<TEntity, bool>> expression = null;

            if (typeof(IAuditedDeletedEntity).IsAssignableFrom(typeof(TEntity)))
            {
                expression = e => !IsSoftDeleteFilterEnabled || !EF.Property<bool>(e, "IsDeleted");
            }

            if (typeof(ITenantEntity).IsAssignableFrom(typeof(TEntity)))
            {
                Expression<Func<TEntity, bool>> multiTenantFilter = e => !IsMultiTenantFilterEnabled || !CurrentTenantId.HasValue || EF.Property<long>(e, "TenantId") == CurrentTenantId;
                expression = expression == null ? multiTenantFilter : CombineExpressions(expression, multiTenantFilter);
            }
             
            return expression;
        }
        protected virtual Expression<Func<T, bool>> CombineExpressions<T>(Expression<Func<T, bool>> expression1, Expression<Func<T, bool>> expression2)
        {
            var parameter = Expression.Parameter(typeof(T));

            var leftVisitor = new ReplaceExpressionVisitor(expression1.Parameters[0], parameter);
            var left = leftVisitor.Visit(expression1.Body);

            var rightVisitor = new ReplaceExpressionVisitor(expression2.Parameters[0], parameter);
            var right = rightVisitor.Visit(expression2.Body);

            return Expression.Lambda<Func<T, bool>>(Expression.AndAlso(left, right), parameter);
        }
        #endregion

        #region 数据预处理

        public virtual void Initialize()
        {

            ChangeTracker.CascadeDeleteTiming = CascadeTiming.OnSaveChanges;
            ChangeTracker.Tracked += ChangeTracker_Tracked;
            ChangeTracker.StateChanged += ChangeTracker_StateChanged;
        }

        public override async Task<int> SaveChangesAsync(bool acceptAllChangesOnSuccess, CancellationToken cancellationToken = default)
        {
            try
            {
                //ApplyConcepts();
                HandlePropertiesBeforeSave();
                var result = await base.SaveChangesAsync(acceptAllChangesOnSuccess, cancellationToken);
                return result;
            }
            catch (DbUpdateConcurrencyException ex)
            {
                throw new SSPivotException(ex.Message, ex);
            }
            finally
            {
                ChangeTracker.AutoDetectChangesEnabled = true;
            }
        }

        public override int SaveChanges(bool acceptAllChangesOnSuccess)
        {
            try
            {
                //ApplyConcepts();
                HandlePropertiesBeforeSave();
                var result = base.SaveChanges(acceptAllChangesOnSuccess);
                return result;
            }
            catch (DbUpdateConcurrencyException ex)
            {
                throw new SSPivotException(ex.Message, ex);
            }
            finally
            {
                ChangeTracker.AutoDetectChangesEnabled = true;
            }
        }

        protected virtual void HandlePropertiesBeforeSave()
        {
            foreach (var entry in ChangeTracker.Entries().ToList())
            {
                if (entry.State.IsIn(EntityState.Modified, EntityState.Deleted))
                {
                    UpdateConcurrencyStamp(entry);
                }
            }
        }

        protected virtual void ChangeTracker_Tracked(object sender, EntityTrackedEventArgs e)
        {
            ApplyConcepts(e.Entry);
        }

        protected virtual void ChangeTracker_StateChanged(object sender, EntityStateChangedEventArgs e)
        {
            ApplyConcepts(e.Entry);
        }

        //protected virtual void ApplyConcepts()
        //{
        //    foreach (var entry in ChangeTracker.Entries().ToList())
        //    {
        //        ApplyConcepts(entry);
        //    }
        //}

        private void ApplyConcepts(EntityEntry entry)
        {
            switch (entry.State)
            {
                case EntityState.Added:
                    ApplyConceptsForAddedEntity(entry);
                    break;
                case EntityState.Modified:
                    ApplyConceptsForModifiedEntity(entry);
                    break;
                case EntityState.Deleted:
                    ApplyConceptsForDeletedEntity(entry);
                    break;
            }
        }

        protected virtual void ApplyConceptsForAddedEntity(EntityEntry entry)
        {
            CheckAndSetId(entry);
            SetTenantIdIfNull(entry);
            SetCreationAuditProperties(entry);
        }

        protected virtual void ApplyConceptsForModifiedEntity(EntityEntry entry)
        {
            if (entry.State == EntityState.Modified && entry.Properties.Any(x => x.IsModified && x.Metadata.ValueGenerated == ValueGenerated.Never))
            {
                SetModificationAuditProperties(entry);

                if (entry.Entity is IAuditedDeletedEntity && entry.Entity.As<IAuditedDeletedEntity>().IsDeleted)
                {
                    SetDeletionAuditProperties(entry);
                }
            }
        }
        protected virtual void ApplyConceptsForDeletedEntity(EntityEntry entry)
        {
            if (!(entry.Entity is IAuditedDeletedEntity))
            {
                return;
            }
            entry.Reload();
            entry.Entity.As<IAuditedDeletedEntity>().IsDeleted = true;
            entry.State = EntityState.Modified;
        }


        protected virtual void SetCreationAuditProperties(EntityEntry entry)
        {
            SetCreationTime(entry);
            SetCreatorId(entry);
            SetCreator(entry);
            SetTenantIdIfNull(entry);
        }
        protected virtual void SetModificationAuditProperties(EntityEntry entry)
        {
            SetLastModificationTime(entry);
            SetLastModifierId(entry);
            SetLastModifier(entry);
            SetTenantIdIfNull(entry); 
        }

        protected virtual void SetDeletionAuditProperties(EntityEntry entry)
        {
            SetDeletionTime(entry);
            SetDeleterId(entry);
            SetDeleter(entry);
        }

        protected virtual void UpdateConcurrencyStamp(EntityEntry entry)
        {
            var entity = entry.Entity as IHasConcurrencyStamp;
            if (entity == null)
            {
                return;
            }

            Entry(entity).Property(x => x.ConcurrencyStamp).OriginalValue = entity.ConcurrencyStamp;
            entity.ConcurrencyStamp = Guid.NewGuid().ToString("N");
        }

        protected virtual void CheckAndSetId(EntityEntry entry)
        {
            if (entry.Entity is IEntity<long> entityWithLongId)
            {
                if (entityWithLongId.Id != default)
                {
                    return;
                }
                var idProperty = entry.Property("Id").Metadata.PropertyInfo;

                //Check for DatabaseGeneratedAttribute
                var dbGeneratedAttr = ReflectionHelper
                    .GetSingleAttributeOrDefault<DatabaseGeneratedAttribute>(
                        idProperty
                    );

                if (dbGeneratedAttr != null && dbGeneratedAttr.DatabaseGeneratedOption != DatabaseGeneratedOption.None)
                {
                    return;
                }

                EntityHelper.TrySetId(
                    entityWithLongId,
                    () => IdGeneratorHelper.IdSnowflake()
                );
            }
        }
       

        protected virtual void SetTenantIdIfNull(EntityEntry entry)
        {
            if (!(entry.Entity is ITenantEntity objectWithTenantId))
            {
                return;
            }

            if (objectWithTenantId.TenantId != null)
            {
                return;
            }
            ObjectHelper.TrySetProperty(objectWithTenantId, x => x.TenantId, () => CurrentTenantId);
        }
         

        private void SetCreationTime(EntityEntry entry)
        {
            if (!(entry.Entity is IAuditedCreatedEntity objectWithCreationTime))
            {
                return;
            }

            if (objectWithCreationTime.CreatedTime != default)
            {
                return;
            }
            ObjectHelper.TrySetProperty(objectWithCreationTime, x => x.CreatedTime, () => DateTime.Now);
        }

        private void SetCreatorId(EntityEntry entry)
        {
            if (!CurrentUser.Id.HasValue)
            {
                return;
            }
             
            if (entry.Entity is IAuditedCreatedEntity mayHaveCreatorObject)
            {
                if (mayHaveCreatorObject.CreatorId.HasValue && mayHaveCreatorObject.CreatorId.Value != default)
                {
                    return;
                }
                ObjectHelper.TrySetProperty(mayHaveCreatorObject, x => x.CreatorId, () => CurrentUser.Id);
            } 
        }
        private void SetCreator(EntityEntry entry)
        {
            if (CurrentUser.Account.IsNullOrEmpty())
            {
                return;
            }
             

            if (entry.Entity is IAuditedCreatedEntity mayHaveCreatorObject)
            {
                if (mayHaveCreatorObject.Creator.IsNotNullOrEmpty() && mayHaveCreatorObject.Creator != default)
                {
                    return;
                }
                ObjectHelper.TrySetProperty(mayHaveCreatorObject, x => x.Creator, () => CurrentUser.Account);
            }
            //else if (entry.Entity is IMustHaveCreator mustHaveCreatorObject)
            //{
            //    if (mustHaveCreatorObject.Creator != default)
            //    {
            //        return;
            //    }
            //    ObjectHelper.TrySetProperty(mustHaveCreatorObject, x => x.Creator, () => CurrentUser.UserName);
            //}
        }

        private void SetLastModificationTime(EntityEntry entry)
        {
            if (entry.Entity is IAuditedModifyEntity objectWithModificationTime)
            {
                objectWithModificationTime.LastModifyTime = DateTime.Now;
            }
        }

        private void SetLastModifierId(EntityEntry entry)
        {
            if (!(entry.Entity is IAuditedModifyEntity modificationAuditedObject))
            {
                return;
            }

            if (!CurrentUser.Id.HasValue)
            {
                modificationAuditedObject.LastModifierId = null;
                return;
            }
             
            modificationAuditedObject.LastModifierId = CurrentUser.Id;
        }
        private void SetLastModifier(EntityEntry entry)
        {
            if (!(entry.Entity is IAuditedModifyEntity modificationAuditedObject))
            {
                return;
            }

            if (CurrentUser.Account.IsNullOrEmpty())
            {
                modificationAuditedObject.LastModifier = null;
                return;
            } 
            modificationAuditedObject.LastModifier = CurrentUser.Account;
        }
        private void SetDeletionTime(EntityEntry entry)
        {
            if (entry.Entity is IAuditedDeletedEntity objectWithDeletionTime)
            {
                if (objectWithDeletionTime.DeletedTime == null)
                {
                    objectWithDeletionTime.DeletedTime = DateTime.Now;
                }
            }
        }
        private void SetDeleterId(EntityEntry entry)
        {
            if (!(entry.Entity is IAuditedDeletedEntity deletionAuditedObject))
            {
                return;
            }

            if (deletionAuditedObject.DeleterId != null)
            {
                return;
            }

            if (!CurrentUser.Id.HasValue)
            {
                deletionAuditedObject.DeleterId = null;
                return;
            }
              
            deletionAuditedObject.DeleterId = CurrentUser.Id;
        }


        private void SetDeleter(EntityEntry entry)
        {
            if (!(entry.Entity is IAuditedFullEntity deletionAuditedObject))
            {
                return;
            }

            if (deletionAuditedObject.Deleter != null)
            {
                return;
            }

            if (CurrentUser.Account.IsNullOrEmpty())
            {
                deletionAuditedObject.Deleter = null;
                return;
            }
              
            deletionAuditedObject.Deleter = CurrentUser.Account;
        }
        #endregion

        #region 私有方法

        private static readonly MethodInfo ConfigureBasePropertiesMethodInfo
   = typeof(AppDbContext<TDbContext>)
       .GetMethod(
           nameof(ConfigureBaseProperties),
           BindingFlags.Instance | BindingFlags.NonPublic
       );
        #endregion

        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            //var config = ConfigHelper.GetOptions<DbConfig>("DbConfig");
            //optionsBuilder.UseDatabase(config.DatabaseType, config.MasterDb);

            //if (ConfigHelper.GetAppSettingBoolean("isSqlLogEnabled"))
            //{
            //    var loggerFactory = new LoggerFactory();
            //    loggerFactory.AddProvider(new EFLoggerProvider());
            //    optionsBuilder.UseLoggerFactory(loggerFactory);
            //} 

#if DEBUG 
            var loggerFactory = new LoggerFactory();
            loggerFactory.AddProvider(new EFLoggerProvider());
            optionsBuilder.UseLoggerFactory(loggerFactory);
#endif

            optionsBuilder.EnableSensitiveDataLogging(); 
            //用于设置是否启用缓存,关闭缓存，每次都会调用OnModelCreating。optionsBuilder.EnableServiceProviderCaching(false);
            base.OnConfiguring(optionsBuilder);
        } 
    }
}
